<template>
  <div
    class="WidgetController"
    :class="{
      isSelected,
      hasChildSelected,
      hasChildren
    }"
    :style="style"
    @mousedown="startDrag($event, 'move')"
    @click="handleWidgetControllerClick"
  >
    <template v-if="isSelected">
      <div
        class="position"
        :class="{
          'anchor-right-top': baseConfig.anchor === 'right-top',
          'anchor-center': baseConfig.anchor === 'center',
          'anchor-right-bottom': baseConfig.anchor === 'right-bottom',
          'anchor-left-bottom': baseConfig.anchor === 'left-bottom',
          'anchor-center-top': baseConfig.anchor === 'center-top',
          'anchor-center-bottom': baseConfig.anchor === 'center-bottom',
          'anchor-left-center': baseConfig.anchor === 'left-center',
          'anchor-right-center': baseConfig.anchor === 'right-center'
        }"
      >
        <div class="x-line" :style="{ height: yLength + 'px' }">
          <div
            class="value"
            :style="{
              top:  '5px'
            }"
          >X：{{baseConfig.x}}</div>
        </div>
        <div class="y-line"
          :style="{
            width: xLength + 'px',
          }">
          <div
            class="value"
            :style="{
              left:  '5px'
            }"
          >Y：{{baseConfig.y}}</div>
        </div>
        <template v-if="!autoSize">
          <span class="control-point top" @mousedown.stop="startDrag($event, 'resize-top')"></span>
          <span class="control-point left" @mousedown.stop="startDrag($event, 'resize-left')"></span>
          <span class="control-point right" @mousedown.stop="startDrag($event, 'resize-right')"></span>
          <span class="control-point bottom" @mousedown.stop="startDrag($event, 'resize-bottom')"></span>
          <span class="control-point top left" @mousedown.stop="startDrag($event, 'resize-top-left')"></span>
          <span class="control-point top right" @mousedown.stop="startDrag($event, 'resize-top-right')"></span>
          <span class="control-point bottom left" @mousedown.stop="startDrag($event, 'resize-bottom-left')"></span>
          <span class="control-point bottom right" @mousedown.stop="startDrag($event, 'resize-bottom-right')"></span>
        </template>
      </div>
    </template>
    <div class="children">
      <WidgetController
        class="child"
        v-for="(child, index) in children"
        :widgetConfig="child"
        :inGroup="true"
        :key="child.id || index"
        :active="isSelected || hasChildSelected"
        :isSelectedWidgetConfigIdFn="isSelectedWidgetConfigIdFn"
        :selectWidgetConfigFn="selectWidgetConfigFn"
        :handleControllerContextmenuFn="handleControllerContextmenuFn"
      ></WidgetController>
    </div>
  </div>
</template>
<script lang="ts">
import { Component, Vue, Inject, Prop, Watch } from 'vue-property-decorator'
import AxisSpace from '@/components/application-editor/axis-space/AxisSpace.vue'
import WidgetConfig from '@/core/model/WidgetConfig';
import ApplicationEditor from '@/components/application-editor/ApplicationEditor.vue';

@Component({
  name: 'WidgetController'
})
export default class WidgetController extends Vue {
  @Inject('app-editor')
  public appEditor!: ApplicationEditor

  @Inject('axis')
  private axis!: AxisSpace;

  @Prop()
  public widgetConfig!: WidgetConfig

  @Prop({ required: false, default: () => false })
  public isSelectedWidgetConfigIdFn!: ( widgetConfig: WidgetConfig ) => boolean

  @Prop({ required: false, default: () => false })
  public selectWidgetConfigFn!: ( widgetConfig: WidgetConfig, multiSelect: boolean) => void

  @Prop({ required: false, default: () => {/** do nothing*/} })
  public handleControllerContextmenuFn!: ( event: MouseEvent) => void

  @Prop({ required: false, default: false })
  private inGroup!: boolean;

  @Prop({ required: false, default: true })
  private active!: boolean

  private move = false

  private dragModel = ''

  private initialState = {
    x: 0,
    y: 0,
    width: 0,
    height: 0,
    clientX: 0,
    clientY: 0,
  }

  get widgetDefinition() {
    return this.appEditor.getWidgetDefinitionBytag(this.widgetConfig._widgetTag)
  }

  get baseConfig () {
    return this.widgetConfig.base
  }

  get isSelected () {
    return this.isSelectedWidgetConfigIdFn(this.widgetConfig)
  }

  get hasChildSelected () {
    return this.children.findIndex(d => this.isSelectedWidgetConfigIdFn(d)) !== -1
  }

  get autoSize() {
    return this.widgetDefinition?.autoSize || false
  }

  get style () {
    const scale = this.axis.scale
    const baseConfig = this.widgetConfig.base
    const runtime = this.widgetConfig._runtime
    const autoSize = this.widgetDefinition?.autoSize

    let width = autoSize ? runtime.w : baseConfig.w
    let height = autoSize ? runtime.h : baseConfig.h

    width = scale * width
    height = scale * height

    const style = {
      width: width + 1 + 'px',
      height: height + 1 + 'px',
      left: Math.round(baseConfig.x) * scale - 0.5 + 'px',
      top: Math.round(baseConfig.y) * scale - 0.5 + 'px',
      marginLeft: '',
      marginTop: ''
    }

    if (baseConfig.anchor === 'right-top') {
      style.marginLeft = `-${width}px`
    }

    if (baseConfig.anchor === 'center') {
      style.marginLeft = `-${width / 2}px`
      style.marginTop = `-${height / 2}px`
    }

    if (baseConfig.anchor === 'left-bottom') {
      style.marginTop = `-${height}px`
    }

    if (baseConfig.anchor === 'right-bottom') {
      style.marginLeft = `-${width}px`
      style.marginTop = `-${height}px`
    }

    if (baseConfig.anchor === 'center-top') {
      style.marginLeft = `-${width / 2}px`
    }

    if (baseConfig.anchor === 'center-bottom') {
      style.marginLeft = `-${width / 2}px`
      style.marginTop = `-${height}px`
    }

    if (baseConfig.anchor === 'left-center') {
      style.marginTop = `-${height / 2}px`
    }

    if (baseConfig.anchor === 'right-center') {
      style.marginLeft = `-${width}px`
      style.marginTop = `-${height / 2}px`
    }

    return style
  }

  get xLength () {
    const scale = this.axis.scale
    const baseConfig = this.baseConfig
    const anchor = baseConfig.anchor

    const baseX = baseConfig.x
    const offsetX = this.axis.offsetX
    let anchorRevise = 0

    if (
      anchor === 'center' ||
      anchor === 'center-top' ||
      anchor === 'center-bottom'
    ) {
      anchorRevise = baseConfig.w / 2
    }

    if (
      anchor === 'right-top' ||
      anchor === 'right-bottom' ||
      anchor === 'right-center'
    ) {
      anchorRevise = baseConfig.w / 2
    }

    if (this.inGroup) {
      return (baseX - anchorRevise) * scale
    } else {
      return (baseX + offsetX - anchorRevise) * scale
    }
  }

  get yLength () {
    const scale = this.axis.scale
    const baseConfig = this.baseConfig
    const anchor = baseConfig.anchor

    const baseY = baseConfig.y
    const offsetY = this.axis.offsetY
    let anchorRevise = 0

    if (
      anchor === 'center' ||
      anchor === 'center-top' ||
      anchor === 'center-bottom'
    ) {
      anchorRevise = baseConfig.h / 2
    }

    if (
      anchor === 'right-top' ||
      anchor === 'right-bottom' ||
      anchor === 'right-center'
    ) {
      anchorRevise = baseConfig.h / 2
    }

    if (this.inGroup) {
      return (baseY - anchorRevise) * scale
    } else {
      return (baseY + offsetY - anchorRevise) * scale
    }
  }

  get children() {
    return this.widgetConfig.children
  }

  get hasChildren() {
    return this.widgetConfig.children.length > 0
  }
 
  public startDrag (event: MouseEvent, dragModel: string) {
    if (!this.isSelected) {
      return
    }

    this.doStartDrag(event, dragModel)

    const controllers = this.axis.controllers as WidgetController[]

    for (const controller of controllers) {
      if (controller !== this && controller.isSelected) {
        controller?.doStartDrag(event, dragModel)
      }
    }

    if (this.inGroup) {
      const group = this.$parent as WidgetController

      const groupRecomputation =  () => {
        if (this.inGroup) {
          const x0 = group.baseConfig.x
          const y0 = group.baseConfig.y
          group.recomputation()
          const xi = group.baseConfig.x - x0
          const yi = group.baseConfig.y - y0
          
          controllers.forEach((d) => {
            const initialState = d.initialState
            if (initialState) {
              initialState.x -= xi
              initialState.y -= yi
            }
          })
        }
      }

      const mouseupDestory = () => {
        document.removeEventListener('mousemove', groupRecomputation)
        document.removeEventListener('mouseup', mouseupDestory)
      }
      document.addEventListener('mousemove', groupRecomputation, { passive: true })
      document.addEventListener('mouseup', mouseupDestory, { passive: true })
    }
  }

  public doStartDrag (event: MouseEvent, dragModel: string) {  
    if (dragModel === 'move') {
      event.stopPropagation()
    }

    this.dragModel = dragModel
    this.initialState = {
      x: this.widgetConfig.base.x,
      y: this.widgetConfig.base.y,
      width: this.widgetConfig.base.w,
      height: this.widgetConfig.base.h,
      clientX: event.clientX,
      clientY: event.clientY
    }

    document.addEventListener('mousemove', this.onMoving, { passive: true })
    document.addEventListener('mouseup', this.endDrag, { passive: true })
  }

  private endDrag (event: MouseEvent) {
    this.onMoving(event)

    this.initialState = {
      x: 0,
      y: 0,
      width: 0,
      height: 0,
      clientX: 0,
      clientY: 0,
    }
    this.dragModel = ''
    window.setTimeout(() => {
      this.move = false
    }, 0)

    document.removeEventListener('mousemove', this.onMoving)
    document.removeEventListener('mouseup', this.endDrag)
  }

  private onMoving(event: MouseEvent) {
    const initialState = this.initialState
    const baseConfig = this.widgetConfig.base
    const scale = this.axis.scale

    let dx = event.clientX - initialState.clientX
    let dy = event.clientY - initialState.clientY

    let vAnchor = 'top'
    let hAnchor = 'left'

    if (baseConfig.anchor === 'right-top') {
      hAnchor = 'right'
    }

    if (baseConfig.anchor === 'center') {
      vAnchor = 'center'
      hAnchor = 'center'
    }

    if (baseConfig.anchor === 'left-bottom') {
      vAnchor = 'bottom'
    }

    if (baseConfig.anchor === 'right-bottom') {
      hAnchor = 'right'
      vAnchor = 'bottom'
    }

    if (baseConfig.anchor === 'center-top') {
      hAnchor = 'center'
      vAnchor = 'top'
    }

    if (baseConfig.anchor === 'center-bottom') {
      hAnchor = 'center'
      vAnchor = 'bottom'
    }

    if (baseConfig.anchor === 'left-center') {
      hAnchor = 'left'
      vAnchor = 'center'
    }

    if (baseConfig.anchor === 'right-center') {
      hAnchor = 'right'
      vAnchor = 'center'
    }

    if (dx !== 0 || dy !== 0) {
      this.move = true
    } else {
      this.move = false
    }

    if (this.dragModel === 'move') {
      this.widgetConfig.base.x = Number((initialState.x + dx / scale).toFixed())
      this.widgetConfig.base.y = Number((initialState.y + dy / scale).toFixed())
    }

    if (
      this.dragModel === 'resize-top' ||
      this.dragModel === 'resize-top-left' ||
      this.dragModel === 'resize-top-right'
    ) {
      if (dy >= initialState.height * scale) {
        dy = initialState.height * scale
      }
      if (vAnchor === 'top') {
        this.widgetConfig.base.y = Number((initialState.y + dy / scale).toFixed())
      }
      this.widgetConfig.base.h = Number((initialState.height - dy / scale).toFixed())
    }

    if (
      this.dragModel === 'resize-bottom' ||
      this.dragModel === 'resize-bottom-left' ||
      this.dragModel === 'resize-bottom-right'
    ) {
      if (dy < -initialState.height * scale) {
        dy = -initialState.height * scale
      }
      if (vAnchor === 'bottom') {
        this.widgetConfig.base.y = Number((initialState.y + dy / scale).toFixed())
      }
      this.widgetConfig.base.h = Number((initialState.height + dy / scale).toFixed())
    }

    if (
      this.dragModel === 'resize-left' ||
      this.dragModel === 'resize-top-left' ||
      this.dragModel === 'resize-bottom-left'
    ) {
      if (dx >= initialState.width * scale) {
        dx = initialState.width * scale
      }
      if (hAnchor === 'left') {
        this.widgetConfig.base.x = Number((initialState.x + dx / scale).toFixed())
      }
      this.widgetConfig.base.w = Number((initialState.width - dx / scale).toFixed())
    }

    if (
      this.dragModel === 'resize-right' ||
      this.dragModel === 'resize-top-right' ||
      this.dragModel === 'resize-bottom-right'
    ) {
      if (dx < -initialState.width * scale) {
        dx = -initialState.width * scale
      }
      if (hAnchor === 'right') {
        this.widgetConfig.base.x = Number((initialState.x + dx / scale).toFixed())
      }
      this.widgetConfig.base.w = Number((initialState.width + dx / scale).toFixed())
    }
  }

  @Watch('children', {immediate: true})
  private childrenChange() {
    window.setTimeout(() => {
      this.recomputation()
    })
  }

  private recomputation () {
    if (this.children.length > 0) {
      const children = this.children
      const getX0 = (d: WidgetConfig) => {
        if (
          d.base.anchor === 'right-top' ||
          d.base.anchor === 'right-center' ||
          d.base.anchor === 'right-bottom'
        ) {
          return d.base.x - d._runtime.w
        }

        if (
          d.base.anchor === 'center-top' ||
          d.base.anchor === 'center' ||
          d.base.anchor === 'center-bottom'
        ) {
          return d.base.x - d._runtime.w / 2
        }
        return d.base.x
      }

      const getY0 = (d: WidgetConfig) => {
        if (
          d.base.anchor === 'left-bottom' ||
          d.base.anchor === 'center-bottom' ||
          d.base.anchor === 'right-bottom'
        ) {
          return d.base.y - d._runtime.h
        }

        if (
          d.base.anchor === 'left-center' ||
          d.base.anchor === 'center' ||
          d.base.anchor === 'right-center'
        ) {
          return d.base.y - d._runtime.h / 2
        }
        return d.base.y
      }
      let x0 = Math.min(...children.map(getX0))
      let y0 = Math.min(...children.map(getY0))

      this.widgetConfig.base.x += x0
      this.widgetConfig.base.y += y0

      for (const child of children) {
        child.base.x -= x0
        child.base.y -= y0
      }

      x0 = Math.min(...children.map(getX0))
      y0 = Math.min(...children.map(getY0))

      const x1 = Math.max(...children.map((d) => {
        const w = d._runtime.w
        return getX0(d) + w
      }))
      const y1 = Math.max(...children.map((d) => {
        const h = d._runtime.h
        return getY0(d) + h
      }))

      this.widgetConfig._runtime.w = x1 - x0
      this.widgetConfig._runtime.h = y1 - y0
    }
  }

  private handleWidgetControllerClick(event: MouseEvent) {
    let isMove = false
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    let current = this as any

    while (
      !isMove &&
      current &&
      typeof current.move === 'boolean'
    ) {
      isMove = current.move
      current = current.$parent || null
    }

    if (isMove) {
      event.stopPropagation()
      return
    }

    if (!this.active) {
      return
    }

    event.stopPropagation()
    this.selectWidgetConfigFn(this.widgetConfig, event.ctrlKey)
  }

  private created() {
    this.axis.controllers.push(this)
  }

  private destroyed() {
    const controllers = this.axis.controllers
    const index = controllers.indexOf(this)
    if (index >= 0) {
      controllers.splice(index, 1);
    }
  }
}
</script>
<style scoped lang="scss">
@import '~@/style/variables.scss';

$selected-color: #ffa500;
$group-color: rgb(0, 165, 255, 0.15);

.WidgetController {
  position: absolute;
  cursor: pointer;
  
  &.hasChildren {
    border: 1px dashed $group-color;
  }

  &.isSelected,
  &.hasChildSelected {
    border: 1px dashed $selected-color;
    background: rgba(0,0,0,0.25);
  }

  .x-line {
    position: absolute;
    left: -1px;
    bottom: 100%;
    width: 1px;
    height: 12000px;
    border-right: dashed 1px $selected-color
  }

  .y-line {
    position: absolute;
    right: 100%;
    bottom: 100%;
    height: 1px;
    width: 12000px;
    border-bottom: dashed 1px $selected-color;
  }

  .position.anchor-right-top .x-line {
    left: 100%;
  }

  .position.anchor-center .x-line {
    left: 50%;
  }

  .position.anchor-center .y-line {
    top: 50%;
  }

  .position.anchor-left-bottom .y-line {
    top: 100%;
  }

  .position.anchor-right-bottom .x-line {
    left: 100%;
  }

  .position.anchor-right-bottom .y-line {
    top: 100%;
  }

  .position.anchor-center-top .x-line {
    left: 50%;
  }

  .position.anchor-center-bottom .x-line {
    left: 50%;
  }

  .position.anchor-center-bottom .y-line {
    top: 100%;
  }

  .position.anchor-left-center .y-line {
    top: 50%;
  }

  .position.anchor-right-center .x-line {
    left: 100%;
  }

  .position.anchor-right-center .y-line {
    top: 50%;
  }

  .value {
    position: absolute;
    display: inline-block;
    padding: 0 5px;
    background: $panel-background-color;
    white-space: nowrap;
    border: solid 1px $border-color;
    border-radius: 0px;
    z-index: 10;
  }

  .x-line .value {
    top: 5px;
    left: 5px;
  }

  .y-line .value {
    top: 5px;
    left: 5px;
  }

  .control-point {
    display: block;
    height: 20px;
    width: 20px;
    // background: green;
    transform-origin: 50% 50%;
    transform: scale(1);
    position: absolute;
    margin-top: -10px;
    margin-left: -10px;
    z-index: 20;
  }

  .control-point.top {
    top: 0%;
    left: 50%;
    cursor: ns-resize;
  }

  .control-point.left {
    top: 50%;
    left: 0%;
    cursor: ew-resize;
  }

  .control-point.bottom {
    top: 100%;
    left: 50%;
    cursor: ns-resize;
  }

  .control-point.right {
    top: 50%;
    left: 100%;
    cursor: ew-resize;
  }

  .control-point.top.left {
    top: 0%;
    left: 0%;
    cursor: nwse-resize;
  }

  .control-point.top.right {
    top: 0%;
    left: 100%;
    cursor: nesw-resize;
  }

  .control-point.bottom.left {
    top: 100%;
    left: 0%;
    cursor: nesw-resize;
  }

  .control-point.bottom.right {
    top: 100%;
    left: 100%;
    cursor: nwse-resize;
  }

  .control-point::after {
    content: '';
    display: block;
    position: absolute;
    top: 6px;
    left: 6px;
    height: 6px;
    width: 6px;
    // border-radius: 100%;
    background: #fff;
    border: 1px solid $selected-color;
  }

  .children {
    position: absolute;
    top: 0;
    left: 0;
  }

  &.hasChildren>.children {
    top: -0.5px;
    left: -0.5px;
  }
}
</style>
